package se.webbzon.boltzmann.game.sequence.builder;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import se.webbzon.boltzmann.environment.AbstractEnvironment;
import se.webbzon.boltzmann.game.sequence.Sequence;
import se.webbzon.boltzmann.game.sequence.SequenceNode;
import se.webbzon.oschi01.friendly.Friendly;
import se.webbzon.oschi01.worldarea.WorldArea;

public class SequenceBuilder {
	
	// The abstract environment associated with this sequence builder
	private final AbstractEnvironment env; 
	
	// A list of all the rows in this sequence builder
	private final List<AbstractSequenceRow> rows;
	
	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates a new sequence builder. **/
	public SequenceBuilder(AbstractEnvironment env) {
		this.env = env;
		rows = new ArrayList<AbstractSequenceRow>();
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Compiles the content of the sequence builder into a sequence. The 
	 * first sequence node in the compiled sequence is returned. **/
	public SequenceNode compile(WorldArea world) {
		Sequence sequence = new Sequence(env,world);
		SequenceNode[] nodes = new SequenceNode[rows.size()];
		
		// Compile the rows
		for (int i = 0; i < nodes.length; i++) {
			AbstractSequenceRow row = rows.get(i);
			nodes[i] = row.compile(world,sequence);
			AbstractSequenceBinding binding = row.getBinding();
			if (binding != null) {
				// Bind the node to an instance
				if (binding.isHard())
					sequence.addBinding(nodes[i],binding.getHard());
				else if (!binding.getSoft().isEmpty()){
					sequence.addBinding(nodes[i], binding.getSoft());
				}
			}
		}
		
		// Bind the nodes together
		for (int i = 0; i < nodes.length; i++)
			rows.get(i).bind(nodes[i],nodes);
		
		return nodes[0];
	}
	
	/** Removes a row from the sequence builder. **/
	public boolean removeRow(AbstractSequenceRow row) {
		// Find the index of the removed row
		int index = rows.indexOf(row);
		
		// Check if the row exists
		if (index >= 0) {
			ListIterator<AbstractSequenceRow> it = rows.listIterator();
			
			// Edit all existing nodes so that they does not point at
			// the removed row
			while (it.hasNext()) {
				AbstractSequenceRow r = it.next();
				
				if (r == row) {
					// The removed row itself
					it.remove();
					
				} else {
					// Edit the links in the row
					for (int i = 0; i < r.links(); i++) {
						Integer link = r.getLink(i);
						if (link != null) {
							if (link == index)
								r.setLink(i, null);
							else if (link > index)
								r.setLink(i, link-1);
						}
					}
				}
			}
			return true;
		} else
			return false;
	}
	
	/** Clears all the rows from the sequence builder. **/
	public void clearRows() {
		rows.clear();
	}
	
	/** Returns the number of rows in this sequence builder. **/
	public int rows() {
		return rows.size();
	}
	
	/** Returns the (n+1):th row of the sequence builder. **/
	public AbstractSequenceRow getRow(int n) {
		return rows.get(n);
	}
	
	/** Returns true if the provided row is a row in this sequence builder. **/
	public boolean containsRow(AbstractSequenceRow row) {
		return rows.contains(row);
	}
	
	/** Moves the row with the index "oldIndex" to the index "newIndex" 
	 * if possible. If the row was successfully moved then true is
	 * returned, otherwise false is returned. **/
	public boolean moveRow(int oldIndex, int newIndex) {
		if (	oldIndex >= 0 && newIndex >= 0 && 
				oldIndex < rows.size() && newIndex < rows.size()) {
			if (oldIndex == newIndex)
				return true;

			final AbstractSequenceRow row = rows.remove(oldIndex);
			final boolean increase;
			final int from;
			final int to;
			if (oldIndex > newIndex) {
				// Move the row downwards
				increase = true;
				from = newIndex;
				to = oldIndex-1;
				rows.add(newIndex, row);
			} else {
				// Move the row upwards
				increase = false;
				from = oldIndex+1;
				to = newIndex;
				rows.add(newIndex-1, row);
			}
			
			// Update links
			for (AbstractSequenceRow r : rows) {
				for (int index = 0; index < r.links(); index++) {
					Integer link = r.getLink(index);
					if (link != null) {
						if (link >= from && link <= to) {
							if (increase)
								r.setLink(index, link+1);
							else
								r.setLink(index, link-1);
						} else if (link == oldIndex) {
							r.setLink(index, newIndex);
						}
					}
				}
			}
			return true;
		} else
			return false;
	}
	
	/*============================================================
	Friendly Methods
	============================================================*/
	
	/** Adds a new row to the sequence builder. This method should only
	 * be called from the constructor of the abstract class 
	 * SequenceBuilderRow. **/
	@Friendly boolean addRow(AbstractSequenceRow row) {
		return rows.add(row);
	}

}
